/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package trinomialtree.math;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import trinomialtree.gui.TrinomialTreeView.mode;

public class TrinomialTree {

    // This is a list of nodes we have already constructed
    public List nodesList = new LinkedList();
    double alpha;
    double deltax;
    double deltat;
    private int DEPTH;
    double sigma = 0.0125d;
    double a = 0.15d;
    double V = 0;
    double[] zeroCurveValues;
    double[] P;
    Node rootNode;
    public mode selectedMode;

    /**
     * This constructor will be called from within Matlab and builds the
     * trinomial tree with the initial values
     */
    public TrinomialTree(List<Double> zinsListe, double deltat, mode selectedMode) {

        this.selectedMode = selectedMode;

        this.deltat = deltat;

        //Transformiere die Zins liste in ein Array mit Zinswerten

        // Wir definieren einstweilen vier Zero-Curve Values
        this.zeroCurveValues = new double[zinsListe.size()];

        Iterator iterator = zinsListe.iterator();
        int j = 0;
        while (iterator.hasNext()) {
            zeroCurveValues[j] = (Double) iterator.next();
            j++;
        }

        // Berechne Barwert Zero Bond Zeitpunkt 0, Endfälligkeit i+1
        this.P = new double[zeroCurveValues.length];
        for (int i = 0; i < zeroCurveValues.length; i++) {
            P[i] = java.lang.Math.exp(-zeroCurveValues[i] * (i + 1) * deltat);

            out(i + " " + P[i]);
        }

    }


    /*
     * CALL Swaption
     */
    public double calculateReceiverSwaption(int laufZeitOption, int laufzeitSwap, int haeufigkeitSettlements, double fixedRate, List<Double> swapRates) throws PayOffNegativeException {

        out("Berechne Receiver Swaption (CALL)");

        //Lege die Tiefe des Baumes fest
        this.DEPTH = (int) (laufZeitOption * 1 / deltat);

        //Erzeuge den Baum
        populateTree();


        //Berechne die SWAP RATE
        //Hole den ersten und den letzten Wert der zero curve values
        double first = swapRates.get(0);
        double last = swapRates.get(swapRates.size() - 1);


        double zaehler = java.lang.Math.exp(-first) - java.lang.Math.exp(-last);

        double sum = 0d;
        for (double d : swapRates) {
            sum += java.lang.Math.exp(-d);
        }
        sum = sum / swapRates.size();

        double swapRate = zaehler / sum;

        double payOff = (swapRate - fixedRate) * sum;
        if (payOff < 0) {
            throw (new PayOffNegativeException());
        }


        for (int i = DEPTH; i >= 0; i--) {
            for (int j = -i; j <= i; j++) {
                Node n = this.getNodeFromList(i, j);
                //Ausnahme für die erste Reihe Knoten
                if (i == DEPTH) {
                    n.swapValue = payOff;
                } else {
                    double swapValue = java.lang.Math.exp(-n.r * deltat) * (n.up.swapValue * n.p_u + n.middle.swapValue * n.p_m + n.down.swapValue * n.p_d);
                    n.swapValue = swapValue;
                }
            }
        }


        return this.getNodeFromList(0, 0).swapValue;
    }

    /*
     * PUT Swaption
     */
    public double calculatePayerSwaption(int laufZeitOption, int laufzeitSwap, int haeufigkeitSettlements, double fixedRate, List<Double> swapRates) throws PayOffNegativeException {

        out("Berechne Payer Swaption (PUT)");

        //Lege die Tiefe des Baumes fest
        this.DEPTH = (int) (laufZeitOption * 1 / deltat);

        //Erzeuge den Baum
        populateTree();


        //Berechne die SWAP RATE
        //Hole den ersten und den letzten Wert der zero curve values
        double first = swapRates.get(0);
        double last = swapRates.get(swapRates.size() - 1);


        double zaehler = java.lang.Math.exp(-first) - java.lang.Math.exp(-last);

        double sum = 0d;
        for (double d : swapRates) {
            sum += java.lang.Math.exp(-d);
        }
        sum = sum / swapRates.size();

        double swapRate = zaehler / sum;

        double payOff = (fixedRate - swapRate) * sum;
        if (payOff < 0) {
            throw (new PayOffNegativeException());
        }


        for (int i = DEPTH; i >= 0; i--) {
            for (int j = -i; j <= i; j++) {
                Node n = this.getNodeFromList(i, j);
                //Ausnahme für die erste Reihe Knoten
                if (i == DEPTH) {
                    n.swapValue = payOff;
                } else {
                    double swapValue = java.lang.Math.exp(-n.r * deltat) * (n.up.swapValue * n.p_u + n.middle.swapValue * n.p_m + n.down.swapValue * n.p_d);
                    n.swapValue = swapValue;
                }
            }
        }


        return this.getNodeFromList(0, 0).swapValue;
    }

    /**
     *
     * @param laufzeitOption
     * @param laufzeitBond
     * @param strike
     * @param nominale
     */
    public double calculateEuropeanPut(int laufzeitOption, int laufzeitBond, double strike, double nominal) {

        out("Berechne European Put");

        //Setze die Tiefe des zu erzeugenden Baumes auf die Laufzeit des Bonds
        this.DEPTH = (int) (laufzeitBond * 1 / deltat);


        //Erzeuge den notwendigen Baum
        populateTree();


        for (int i = DEPTH; i >= 0; i--) {
            for (int j = -i; j <= i; j++) {
                Node n = this.getNodeFromList(i, j);


                //Ausnahme für die erste Reihe Knoten
                if (i == DEPTH) {
                    n.bondValue = nominal;
                } else {
                    double bondvalue = java.lang.Math.exp(-n.r * deltat) * (n.up.bondValue * n.p_u + n.middle.bondValue * n.p_m + n.down.bondValue * n.p_d);
                    n.bondValue = bondvalue;
                }
            }
        }


        //Berechne PUT
        int k = (int) (laufzeitOption * 1 / deltat);
        for (int i = k; i >= 0; i--) {
            for (int j = -i; j <= i; j++) {
                Node n = this.getNodeFromList(i, j);


                if (i == k) {
                    if (strike - n.bondValue < 0) {
                        n.putValue = 0;
                    } else {
                        n.putValue = strike - n.bondValue;
                    }

                } else {
                    double putValue = java.lang.Math.exp(-n.r * deltat) * (n.up.putValue * n.p_u + n.middle.putValue * n.p_m + n.down.putValue * n.p_d);

//					if ((strike - n.bondValue) > putValue) {
//						n.putValue = strike - n.bondValue;
//					}
//					else {
                    n.putValue = putValue;
//					}
                }

            }
        }

        //Retournier den call value des 0,0 Knotens
        return this.getNodeFromList(0, 0).putValue;

    }

    /**
     *
     * @param laufzeitOption
     * @param laufzeitBond
     * @param strike
     * @param nominale
     */
    public double calculateEuropeanCall(int laufzeitOption, int laufzeitBond, double strike, double nominal) {

        out("Berechne European Call");

        //Setze die Tiefe des zu erzeugenden Baumes auf die Laufzeit des Bonds
        this.DEPTH = (int) (laufzeitBond * 1 / deltat);

        //Erzeuge den notwendigen Baum
        populateTree();


        for (int i = DEPTH; i >= 0; i--) {
            for (int j = -i; j <= i; j++) {
                Node n = this.getNodeFromList(i, j);


                //Ausnahme für die erste Reihe Knoten
                if (i == DEPTH) {
                    n.bondValue = nominal;
                } else {
                    double bondvalue = java.lang.Math.exp(-n.r * deltat) * (n.up.bondValue * n.p_u + n.middle.bondValue * n.p_m + n.down.bondValue * n.p_d);
                    n.bondValue = bondvalue;
                }
            }
        }


        //Berechne CALL
        int k = (int) (laufzeitOption * 1 / deltat);
        for (int i = k; i >= 0; i--) {
            for (int j = -i; j <= i; j++) {
                Node n = this.getNodeFromList(i, j);


                if (i == k) {
                    if (n.bondValue - strike < 0) {
                        n.callValue = 0;
                    } else {
                        n.callValue = n.bondValue - strike;
                    }

                } else {
                    double callValue = java.lang.Math.exp(-n.r * deltat) * (n.up.callValue * n.p_u + n.middle.callValue * n.p_m + n.down.callValue * n.p_d);

//					if ((strike - n.bondValue) > putValue) {
//						n.putValue = strike - n.bondValue;
//					}
//					else {
                    n.callValue = callValue;
//					}
                }

            }
        }




        //Retournier den call value des 0,0 Knotens
        return this.getNodeFromList(0, 0).callValue;



    }

    /**
     * Erzeuge eine Liste mit allen Knoten des Baumes
     *
     * @param depth
     */
    private void populateTree() {

        calculateIntialValues();


        for (int i = 0; i <= DEPTH; i++) {
            for (int j = -i; j <= i; j++) {
                Node n = new Node(i, j);
                this.nodesList.add(n);
            }
        }

        // Bestimme die Nachfolger für jeden Knoten und berechne die
        // Intitialwerte
        for (int i = 0; i <= DEPTH; i++) {
            for (int j = -i; j <= i; j++) {
                Node n = getNodeFromList(i, j);

                // Berechne x_j
                double x_j = new Double(j * deltax).doubleValue();
                n.x_j = x_j;

                // Berechne m_j
                double m_j = x_j * java.lang.Math.exp(-a * deltat);
                n.m_j = m_j;

                // Berechne k
                int k = (int) java.lang.Math.round(m_j / deltax);
                n.k = k;

                // Berechne die Nachfolgerknoten
                Node dummy1 = getNodeFromList(i + 1, k + 1);

                n.up = dummy1;
                if (dummy1 != null) {
                    dummy1.getParentNodes().add(n);
                }

                Node dummy2 = getNodeFromList(i + 1, k);
                n.middle = dummy2;
                if (dummy2 != null) {
                    dummy2.getParentNodes().add(n);
                }

                Node dummy3 = getNodeFromList(i + 1, k - 1);
                n.down = dummy3;
                if (dummy3 != null) {
                    dummy3.getParentNodes().add(n);
                }

            }
        }

        //Berechne die Initalwerte des Baumes
        calculateTree();


    }

    /**
     * Berechne den Baum
     */
    private void calculateTree() {

        // Berechne den Baum (Intialwerte)
        for (int i = 0; i <= DEPTH; i++) {
            for (int j = -i; j <= i; j++) {
                Node n = getNodeFromList(i, j);

                // Hole das x_k
                Node dummy = getNodeFromList(i, n.k);
                double x_k = dummy.x_j;

                // Berechne den eta Wert für jeden Knoten
                double eta_j = n.m_j - x_k;
                n.eta_j = eta_j;

                n.p_u = (1d / 6d) + ((eta_j * eta_j) / (6 * V * V))
                        + (eta_j / (2 * java.lang.Math.sqrt(3) * V));
                n.p_m = (2d / 3d)
                        - (java.lang.Math.pow(eta_j, 2) / (3 * java.lang.Math.pow(V, 2)));
                n.p_d = (1d / 6d) + java.lang.Math.pow(eta_j, 2)
                        / (6 * java.lang.Math.pow(V, 2))
                        - (eta_j / (2 * java.lang.Math.sqrt(3) * V));

            }
        }

        // Berechne nun die Q und r Werte
        double previousAlpha = alpha;


        for (int i = 0; i <= DEPTH; i++) {

            double temporaryAlpha = 0d;

            for (int j = -i; j <= i; j++) {
                Node n = getNodeFromList(i, j);

                // Rootknoten wir eigens behandelt
                if (n.j == 0 && n.i == 0) {
                    n.Q = 1;
                    n.r = alpha;

                    break;
                } else {

                    // Hole pro Knoten die Queue mit den Vorgängerknoten
                    List<Node> parentNodes = n.getParentNodes();
                    double parentSum = 0d;

                    for (Node parentNode : parentNodes) {

                        // Was bin ich für den ParentNode (down, middle, or up)?
                        if (parentNode.down == n) {
                            parentSum += parentNode.Q
                                    * parentNode.p_d
                                    * java.lang.Math.exp((-1)
                                    * (parentNode.j * deltax + previousAlpha) * deltat);
                        } else if (parentNode.up == n) {
                            parentSum += parentNode.Q
                                    * parentNode.p_u
                                    * java.lang.Math.exp((-1)
                                    * (parentNode.j * deltax + previousAlpha) * deltat);
                        } else {
                            parentSum += parentNode.Q
                                    * parentNode.p_m
                                    * java.lang.Math.exp((-1)
                                    * (parentNode.j * deltax + previousAlpha) * deltat);
                        }

                    }

                    // Q fertig berechnet - setze den Wert für den Knoten
                    n.Q = parentSum;
                    temporaryAlpha += n.Q * java.lang.Math.exp((-1) * n.j * deltax * deltat);

                    //Berechne den r  Wert
                    n.r = n.j * deltax + previousAlpha;

                }



            }


            // Alle Knoten einer Reihe sind fertig - das neue alpha kann gesetzt
            // werden - außer im 1ersten Jahr
            if (i != 0) {
                previousAlpha = (1 / deltat) * (java.lang.Math.log(temporaryAlpha / P[i]));
            }


            //Berechne in einem zweiten Durchgang die r Werte
            for (int j = -i; j <= i; j++) {
                Node n = getNodeFromList(i, j);
                //Berechne den r  Wert
                n.r = n.j * deltax + previousAlpha;

            }
        }

    }

    /**
     * Print the final tree
     * mode 0 = option put
     * mode 1 = option call
     * mode 2 = swaption put
     * mode 3 = swaption call
     */
    public void printTree(int mode) {

        if (nodesList == null || nodesList.size() == 0) {
            out("Tree is empty.");
            return;
        }

        out("Gesamtknoten: " + nodesList.size() + "\n");

        Iterator iterator = nodesList.iterator();
        while (iterator.hasNext()) {
            Node n = (Node) iterator.next();

            if (mode == 0) {
                out(n.toString() + " Putvalue: " + n.putValue + " Bondvalue: " + n.bondValue);
            } else if (mode == 1) {
                out(n.toString() + " Callvalue: " + n.callValue + " Bondvalue: " + n.bondValue);
            } else if (mode == 2) {
                out(n.toString() + " Swapvalue: " + n.swapValue);
            } else if (mode == 3) {
                out(n.toString() + " Swapvalue: " + n.swapValue);
            }


            // if (n.parentNodes.isEmpty()) {
            // out("Keine Vorgänger.");
            // } else {
            // out("Vorgängerknoten");
            // while (!n.parentNodes.isEmpty()) {
            // try {
            // Node parentNode = (Node) n.parentNodes.dequeue();
            // out("Parent: " + parentNode.toString());
            // } catch (InterruptedException e) {
            // // TODO Auto-generated catch block
            // e.printStackTrace();
            // }
            // }
            // }

        }

    }

    /**
     * Berechne die Intialwerte für die Erstellung des Baumes
     */
    private void calculateIntialValues() {

        // Berechne das V
        this.V = java.lang.Math.sqrt((sigma * sigma) / (2 * a)
                * (1 - java.lang.Math.exp(-2 * a * deltat)));

        // Berechne deltax
        this.deltax = sigma
                * java.lang.Math.sqrt((3 / (2 * a))
                * (1 - java.lang.Math.exp(-2 * a * deltat)));



        this.alpha = ((-1d) * java.lang.Math.log(P[0])) / deltat;




    }

    /*
     * Checks whether a node has already been visited
     */
    public boolean alreadyVisited(List visitedNodes, Node n) {

        Iterator iterator = visitedNodes.iterator();
        while (iterator.hasNext()) {
            Node current = (Node) iterator.next();
            if (current == n) {
                return true;
            }
        }

        return false;
    }

    /**
     * Populate a trinomial tree with the given depth
     *
     * @param depth
     */
    private Node populateTree(int depth, List nodesList) {

        // This is the root node
        Node rootNode = new Node(0, 0);

        addNodes(rootNode, depth, nodesList);

        return rootNode;
    }

    /**
     *
     * @param rootNodev
     * @param depth
     *            The maximum value of x
     * @return
     */
    private Node addNodes(Node rootNode, int depth, List nodesList) {

        if (rootNode.i < depth) {

            Node upNode = getNodeFromListIfExists(rootNode.i + 1,
                    rootNode.j + 1, nodesList);
            if (upNode == null) {
                rootNode.up = addNodes(
                        new Node(rootNode.i + 1, rootNode.j + 1), depth,
                        nodesList);
            } else {
                rootNode.up = upNode;
            }
            rootNode.up.getParentNodes().add(rootNode);

            Node middleNode = getNodeFromListIfExists(rootNode.i + 1,
                    rootNode.j, nodesList);
            if (middleNode == null) {
                rootNode.middle = addNodes(
                        new Node(rootNode.i + 1, rootNode.j), depth, nodesList);
            } else {
                rootNode.middle = middleNode;
            }
            rootNode.middle.getParentNodes().add(rootNode);

            Node downNode = getNodeFromListIfExists(rootNode.i + 1,
                    rootNode.j - 1, nodesList);
            if (downNode == null) {
                rootNode.down = addNodes(new Node(rootNode.i + 1,
                        rootNode.j - 1), depth, nodesList);
            } else {
                rootNode.down = downNode;
            }
            rootNode.down.getParentNodes().add(rootNode);

        }

        nodesList.add(rootNode);

        return rootNode;
    }

    /**
     * Bevor wir den Baum zeichnen entfernen wir aus der Knotenliste all jene,
     * die keinen Vorgänger haben
     */
    public void deleteNodesWithoutPredecessors() {
        //Zeichenroutine links nach rechts
        for (int i = 0; i <= this.DEPTH; i++) {
            for (int j = -i; j <= i; j++) {
                Node n = getNodeFromList(i, j);

                if (i != 0 && j != 0) {

                    if (n.getParentNodes() == null || n.getParentNodes().size() == 0) {
                        this.nodesList.remove(n);

                        //Teile meinen Nachfolgern mit, dass ich als Knoten nicht mehr existent bin
                        if (n.up != null)
                            n.up.parentNodes.remove(n);
                        if (n.middle != null)
                            n.middle.parentNodes.remove(n);
                        if (n.down != null)
                            n.down.parentNodes.remove(n);

                    }
                }
            }

        }
    }

    /**
     * Get a node from the list of the tree nodes based on i and j
     *
     * @param i
     * @param j
     * @return
     */
    public Node getNodeFromList(int i, int j) {

        Iterator iterator = nodesList.iterator();
        while (iterator.hasNext()) {
            Node n = (Node) iterator.next();

            if (n.i == i && n.j == j) {
                return n;
            }

        }

        return null;

    }

    /**
     * Returns the node with the appropriate x and y values or null instead
     *
     * @param x
     * @param y
     * @param nodesList
     * @return
     */
    private Node getNodeFromListIfExists(int i, int j, List nodesList) {

        Iterator iterator = nodesList.iterator();
        while (iterator.hasNext()) {
            Node n = (Node) iterator.next();

            if (n.i == i && n.j == j) {
                return n;
            }

        }

        return null;

    }

    private void out(String s) {
        System.out.println(s);
    }

    private void out(String s, boolean suppressLineFeed) {
        System.out.print(s);
    }

    private void out(Double s) {
        System.out.println(s);
    }

    private void out(Integer s) {
        System.out.println(s);
    }

    private class eta {

        int j;
        int k;
        double value;
    }

    public int getDepth() {
        return this.DEPTH;
    }
}
